/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package persistencia;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.sql.ResultSet;
import persistencia.mecanismos.PersistenceMechanism;
import persistencia.mecanismos.RelationalDatabase;
import java.io.File;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import persistencia.mapeo.*;
import persistencia.sentencias.SqlStatement;
import persistencia.sentencias.SqlStatementDelete;
import persistencia.sentencias.SqlStatementInsert;
import persistencia.sentencias.SqlStatementSelect;

public class PersistenceBroker {

    private static Map mappings;
    private static PersistenceBroker instacia;
    private List<PersistenceMechanism> persistencias;
    private DatabaseMap dbMap = null;
    private String nombreClase;

    public static Map getMappings() {
        return mappings;
    }

    public static PersistenceBroker getInstacia() {
        if (instacia == null) {
            instacia = new PersistenceBroker();
        }
        return instacia;
    }

    public List<PersistenceMechanism> getPersistencias() {
        return this.persistencias;
    }

    private PersistenceBroker() {
        mappings = new HashMap();
        persistencias = new ArrayList();
    }

    public void leerSchemas(String directorio) {
        try {

            schemaReader(directorio);

        } catch (Exception ex) {
            Logger.getLogger(PersistenceBroker.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void leerPersistencias(String directorio) {
        try {
            databaseReader(directorio);
        } catch (Exception ex) {
            Logger.getLogger(PersistenceBroker.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    //BORRAR LUEGO DE TESTEADAS TODAS LAS SENTENCIAS
    public void databaseReader(String ubicacionArchivo) throws Exception {
        String bd = "", login = "", password = "", url = "", driver = "";
        SAXBuilder builder = new SAXBuilder();
        File schemaFile = new File(ubicacionArchivo);
        Document schemaDoc = builder.build(schemaFile);
        Element root = schemaDoc.getRootElement();
        Element mecanismos = root.getChild("mecanismosdepersistencia");
        List basesdedatos = mecanismos.getChildren("basesdedatos");
        for (int i = 0; i < basesdedatos.size(); i++) {
            Element bases = (Element) basesdedatos.get(i);
            List listaBases = bases.getChildren("base");
            for (int j = 0; j < listaBases.size(); j++) {
                Element datos = (Element) listaBases.get(j);
                bd = datos.getChild("bd").getText();
                driver = datos.getChild("driver").getText();
                login = datos.getChild("login").getText();
                password = datos.getChild("password").getText();
                url = datos.getChild("url").getText();
                contruirMecanismo(bd, driver, login, password, url);
            }
        }
    }

    //Se podria hacer el switch por mecanismo de peristencia, no por cada subdivision de los mismos.
    public void contruirMecanismo(String nombre, String driver, String login, String password, String url) {
        persistencias.add(new RelationalDatabase(nombre, driver, login, password, url));
    }

    public void schemaReader(String ubicacionArchivo) throws Exception {
        SAXBuilder builder = new SAXBuilder();
        File schemaFile = new File(ubicacionArchivo);
        Document schemaDoc = builder.build(schemaFile);
        Element rootElement = schemaDoc.getRootElement();
        Element classesElement = rootElement.getChild("clases");
        List classes = classesElement.getChildren("clase");



        //agregar mapeo a databaseMap(crearlo) luego asociarle un tableMap y luego el columnMap que tenemos en el AttributeMap
        for (int i = 0; i < classes.size(); i++) {
            String nombre = "", tipo = "", columna = "", databaseMap = "", nombreClase = "";
            int clave = 0;

            ClassMap classMap = new ClassMap();
            Element classElement = (Element) classes.get(i);

            nombreClase = classElement.getChild("nombre").getText();

            classMap.setNombre(nombreClase);

            //Sacamos el nombre de la base de datos en la que guardaremos la clase
            databaseMap = classElement.getChild("mecanismodepersistencia").getText();

            Element atributosElement = classElement.getChild("atributos");
            List atributos = atributosElement.getChildren("atributo");
            for (int j = 0; j < atributos.size(); j++) {
                AttributeMap mapaAtributos = new AttributeMap();
                Element attributeElement = (Element) atributos.get(j);
                nombre = attributeElement.getChild("nombre").getText();
                tipo = attributeElement.getChild("tipo").getText();
                clave = Integer.valueOf(attributeElement.getChild("clave").getText());
                columna = attributeElement.getChild("columna").getText();
                mapaAtributos.setNombre(nombre);
                mapaAtributos.setTipo(tipo);
                mapaAtributos.setClave(clave);
                mapaAtributos.setColumna(new ColumnMap(columna, mapaAtributos.esClave(), tipo));

                establecerRelaciones(databaseMap, nombreClase, nombre, mapaAtributos.esClave(), tipo);

                classMap.getAtributos().add(mapaAtributos);
            }
            getMappings().put(classMap.getNombre(), classMap);
        }
    }

    public void establecerRelaciones(String databaseMap, String nombreTabla, String nombreColumna, boolean esClave, String tipo) {
        if (dbMap == null) {
            dbMap = new DatabaseMap(databaseMap);
        }
        dbMap.addTableMap(nombreTabla); //Esto solo sirve la primera vez, ya que en la siguiente linea hardcodeamos
        //a que tabla queremos agregarle las columnas. Deberia flexibilizarse!
        dbMap.getTableMap().get(0).addColumnMap(nombreColumna, esClave, tipo);
    }

    public List<String> retrieveObject(PersistentObject objeto) throws Exception {
        List<String> valores = new ArrayList();
        String className = objeto.getClass().getName();
        ClassMap classMap = (ClassMap)mappings.get(className);
        for (int i = 0; i < classMap.getAtributos().size(); i++) {
            AttributeMap atr = (AttributeMap) classMap.getAtributos().get(i);
            valores.add(obtenerValores(atr.getNombre(), objeto));
        }
        return valores;
    }

    private String obtenerValores(String nombre, PersistentObject objeto) throws Exception {
        String retorno = "";
        BeanInfo beanInfo = Introspector.getBeanInfo(objeto.getClass());
        for (int i = 0; i < beanInfo.getPropertyDescriptors().length; i++) {
            PropertyDescriptor descriptor = beanInfo.getPropertyDescriptors()[i];
            Method getMethod = descriptor.getReadMethod();
            if (descriptor.getName().equalsIgnoreCase(nombre)) {
                Object obj = getMethod.invoke(objeto, new Object[0]);
                retorno = obj.toString();
            }
        }
        return retorno;
    }

    public void saveObject(PersistentObject objeto) throws Exception {
        SqlStatement sql = new SqlStatementInsert();
        String sentencia = sql.generarStatement(nombreClase(objeto), retrieveObject(objeto));
        prepararInsert(sentencia, retrieveObject(objeto));
    }

    public String nombreClase(PersistentObject objeto) {
        String clase = objeto.getClass().getName();
        StringTokenizer tokens = new StringTokenizer(clase, ".");
        String nombreClase = "";
        while (tokens.hasMoreTokens()) {
            nombreClase = tokens.nextToken();
        }
        return nombreClase;
    }

    public void prepararInsert(String sql, List<String> atributos) throws SQLException {

        PersistenceMechanism pmech = persistencias.get(0);

        pmech.open();

        Connection con = pmech.getConnection();

        con.setAutoCommit(false);

        con.createStatement();

        PreparedStatement prep = con.prepareStatement(sql);

        for (int i = 0; i < atributos.size(); i++) {
            prep.setObject(i + 1, atributos.get(i));
        }

        ejecutarSentencia(prep, con);

        System.out.println(sql);

    }

    //Utilizar para cuando se crea la tabla, asi se reutiliza codigo
    public void ejecutarSentencia(PreparedStatement stm, Connection con) throws SQLException {

        stm.execute();

        con.commit();

        stm.close();

        con.close();

    }

    public void deleteObject(PersistentObject objeto) throws Exception {
        SqlStatement sql = new SqlStatementDelete();
        String sentencia = sql.generarStatement(nombreClase(objeto), retornarColumnaId(), retrieveObject(objeto).get(0));
        prepararDelete(sentencia, retrieveObject(objeto));
    }

    public void prepararDelete(String sql, List<String> atributos) throws SQLException {

        PersistenceMechanism pmech = persistencias.get(0);

        pmech.open();

        Connection con = pmech.getConnection();

        con.setAutoCommit(false);

        con.createStatement();

        PreparedStatement prep = con.prepareStatement(sql);

        ejecutarSentencia(prep, con);

        System.out.println(sql);

    }

    public String retornarColumnaId() {
        String retorno = "";
        Iterator it = mappings.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry e = (Map.Entry) it.next();
            ClassMap m = (ClassMap) e.getValue();
            for (int i = 0; i < m.getAtributos().size(); i++) {
                if (m.getAtributos().get(i).esClave()) {
                    retorno += m.getAtributos().get(i).getColumna().getNombre();
                }
            }
        }
        return retorno;
    }

    public void updateObject(PersistentObject objeto) throws Exception {
        SqlStatement sql = new SqlStatementDelete();
        String sentencia = sql.generarStatement(nombreClase(objeto), retornarColumnaId(), retrieveObject(objeto).get(0));
        prepararDelete(sentencia, retrieveObject(objeto));
        sql = new SqlStatementInsert();
        sentencia = sql.generarStatement(nombreClase(objeto), retrieveObject(objeto));
        prepararInsert(sentencia, retrieveObject(objeto));
    }

    //No se usan
    public void connectTo(PersistenceMechanism mecanismo) {
        mecanismo.open();
    }

    public void disconnectFrom(PersistenceMechanism mecanismo) {
        mecanismo.close();
    }

    void selectObject(PersistentObject objeto) throws Exception {
        SqlStatement sql = new SqlStatementSelect();
        String sentencia = sql.generarStatement(nombreClase(objeto), retornarColumnaId(), retrieveObject(objeto).get(0));
        ResultSet rs = prepararSelect(sentencia, objeto);
    }

    public ResultSet prepararSelect(String sql, PersistentObject objeto) throws SQLException {

        ResultSet rs = null;

        PersistenceMechanism pmech = persistencias.get(0);

        pmech.open();

        Connection con = pmech.getConnection();

        con.setAutoCommit(false);

        con.createStatement();

        PreparedStatement prep = con.prepareStatement(sql);

        //ejecutarSentencia(prep, con);

        System.out.println(sql);

        rs = prep.executeQuery();

        con.commit();

        while (rs.next()) {
            Iterator it = mappings.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry e = (Map.Entry) it.next();
                ClassMap m = (ClassMap) e.getValue();
                for (int i = 0; i < m.getAtributos().size(); i++) {
                    System.out.println(rs.getObject(m.getAtributos().get(i).getColumna().getNombre()));
                }
            }
        }

        rs.close();

        prep.close();

        con.close();

        return rs;

    }
}